<?php
//-------------------------------------------------------------------------
// OVIDENTIA http://www.ovidentia.org
// Ovidentia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
// 
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//-------------------------------------------------------------------------
/**
 * @license http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
 * @copyright Copyright (c) 2006 by CANTICO ({@link http://www.cantico.fr})
 */
include_once 'base.php';

require_once $GLOBALS['babInstallPath'] . '/utilit/dateTime.php';
require_once $GLOBALS['babInstallPath'] . '/addons/widgets/widgets/widget.class.php';
require_once $GLOBALS['babInstallPath'] . '/addons/widgets/widgets/color.class.php';
require_once $GLOBALS['babInstallPath'] . '/addons/widgets/widgets/label.class.php';
require_once $GLOBALS['babInstallPath'] . '/addons/widgets/widgets/frame.class.php';
require_once $GLOBALS['babInstallPath'] . '/addons/widgets/widgets/hboxlayout.class.php';
require_once $GLOBALS['babInstallPath'] . '/addons/widgets/widgets/vboxlayout.class.php';




/**
 * Constructs a Widget_Calendar.
 *
 * @param string $id			The item unique id.
 * @return Widget_Calendar
 */
function Widget_Calendar($id = null)
{
	return new Widget_Calendar($id);
}



/**
 *
 */
class Widget_Calendar extends Widget_Widget implements Widget_Displayable_Interface 
{

	/**
	 * The calendar periods
	 * @var array	of Widget_CalendarPeriod
	 */
	private $_periods = array();

	/**
	 * The day that should be displayed by the calendar.
	 * @var BAB_DateTime
	 */
	private $_date;
	
	/**
	 * The first day of the week (0-6) displayed in a week view. 
	 * @var int
	 */
	private $_firstDayOfWeek = 0;

	/**
	 * The number of days displayed.
	 * @var int
	 */
	private $_nbDisplayedDays = 5;

	/**
	 * The height of an hour in pixels.
	 * @var int
	 */
	private $_hourHeight = 30;
	private $_hourHeightUnit = 'px';
	
	/**
	 * The first hour displayed by the calendar for each day
	 * @var int
	 */
	private $_displayStartHour = 0;

	/**
	 * The number of hours displayed by the calendar for each day
	 * @var int
	 */
	private $_nbDisplayedHours = 24;
	
	/**
	 * @param string $id			The item unique id.
	 * @return Widget_Calendar
	 */
	public function __construct($id = null)
	{
		parent::__construct($id);

	}



	/**
	 * Sets the day that should be displayed by the calendar.
	 *
	 * @param BAB_DateTime $startDate
	 * @return Widget_Calendar
	 */
	public function setDate(BAB_DateTime $startDate)
	{
		 $this->_date = $startDate;
		 return $this;
	}



	/**
	 * Returns the day that should be displayed by the calendar.
	 *
	 * @return BAB_DateTime
	 */
	public function getDate()
	{
		if (!isset($this->_date)) {
			$this->setDate(BAB_DateTime::now());
		}
		return $this->_date;
	}



	/**
	 * Sets the first day of the week (0-6) displayed in a week view. 
	 *
	 * @param int $startDayOfWeek	a number in [0-6] or null to unset.
	 * @return Widget_Calendar
	 */
	public function setFirstDayOfWeek($firstDayOfWeek)
	{
		assert('is_null($firstDayOfWeek) || (is_numeric($firstDayOfWeek) && (int)$firstDayOfWeek >= 0 && (int)$firstDayOfWeek < 7); /* The "firstDayOfWeek" parameter must be a numeric value between 0 and 6 or null. */');
		$this->_firstDayOfWeek = $firstDayOfWeek;
		return $this;
	}



	/**
	 * Returns the first day of the week (0-6) displayed in the week view. 
	 *
	 * @return int		or null
	 */
	public function getFirstDayOfWeek()
	{
		return $this->_firstDayOfWeek;
	}



	/**
	 * Sets the hour displayed height in pixels. 
	 *
	 * @param int $height
	 * @return Widget_Calendar
	 */
	public function setHourHeight($height)
	{
		assert('is_numeric($height); /* The "height" parameter must be a numeric value. */');
		$this->_hourHeight = (int)$height;
		return $this;
	}



	/**
	 * Returns the hour displayed height in pixels. 
	 *
	 * @return int
	 */
	public function getHourHeight()
	{
		return $this->_hourHeight;
	}



	/**
	 * Sets the first hour displayed by the calendar for each day.
	 *
	 * @param int $hour		The first displayed hour (0-23).
	 */
	public function setDisplayStartHour($hour)
	{
		assert('is_numeric($hour) && (int)$hour >= 0 && (int)$hour < 24; /* The "hour" parameter must be a numeric value between 0 and 23. */');
		$this->_displayStartHour = (int)$hour;
		return $this;
	}



	/**
	 * Returns the first hour displayed by the calendar for each day.
	 *
	 * @return int
	 */
	public function getDisplayStartHour()
	{
		return $this->_displayStartHour;
	}



	/**
	 * Sets the number of hours displayed by the calendar for each day.
	 *
	 * @param int $hours		The first displayed hour (0-24).
	 */
	public function setNbDisplayedHours($hours)
	{
		assert('is_numeric($hours) && (int)$hours >= 0 && (int)$hours <= 24; /* The "hours" parameter must be a numeric value between 0 and 24. */');
		$this->_nbDisplayedHours = (int)$hours;
		return $this;
	}



	/**
	 * Returns the number of hours displayed by the calendar for each day.
	 *
	 * @return int
	 */
	public function getNbDisplayedHours()
	{
		return $this->_nbDisplayedHours;
	}



	/**
	 * Sets the number of days displayed by the calendar.
	 *
	 * @param int $hours		The first displayed hour (0-24).
	 */
	public function setNbDisplayedDays($days)
	{
		assert('is_numeric($days) && (int)$days >= 0; /* The "days" parameter must be a numeric value greater than 0. */');
		$this->_nbDisplayedDays = (int)$days;
		return $this;
	}



	/**
	 * Returns the number of days displayed by the calendar.
	 *
	 * @return int
	 */
	public function getNbDisplayedDays()
	{
		return $this->_nbDisplayedDays;
	}



	/**
	 * Adds a period to the calendar.
	 *
	 * @param Widget_CalendarPeriod $period
	 * @return Widget_Calendar
	 */
	function addPeriod(Widget_CalendarPeriod $period)
	{
		$this->_periods[] = $period;
		return $this;
	}



	/**
	 * Creates a new period.
	 *
	 * @param BAB_DateTime $startDate
	 * @param BAB_DateTime $endDate
	 * @return Widget_CalendarPeriod
	 */
	function createPeriod($startDate, $endDate)
	{
		return new Widget_CalendarPeriod($this, $startDate, $endDate);
	}


	public function onDoubleClick(widget_Action $action)
	{
		$this->onDoubleClick = $action;
		$this->setMetadata('doubleClick', $action->url());
	} 


	public function onPeriodMoved(widget_Action $action)
	{
		$this->onPeriodMoved = $action;
		$this->setMetadata('periodMoved', $action->url());
	} 


	public function getClasses()
	{
		$classes = parent::getClasses();
		$classes[] = 'widget-calendar';
		return $classes;
	}



	public function displayCalendarHeader(Widget_Canvas $canvas, bab_DateTime $startDate)
	{
		$date = $startDate->cloneDate();

		$todayIso = bab_DateTime::now()->getIsoDate();
		
		$previousYear = null;
		$cols = array();
		$cols[] = $canvas->gridRowCell($this->getHtmlId() . '__', array());
		for ($day = 0; $day  < $this->_nbDisplayedDays; $day++) {
			if ($date->getYear() !== $previousYear) {
				$daySpan = $canvas->span('', array(), array(bab_formatDate('%d %j %m %Y', $date->getTimeStamp())));
				$previousYear = $date->getYear();
			} else {
				$daySpan = $canvas->span('', array(), array(bab_formatDate('%d %j %m', $date->getTimeStamp())));
			}
			
			$isToday = ($date->getIsoDate() === $todayIso);
			$classnames = array('widget-align-center');
			if ($date->getIsoDate() === $todayIso) {
				$classnames[] = 'widget-calendar-today';
			}
			if ($day >= 5) {
				$classnames[] = 'widget-calendar-weekend';
			}
			$cols[] = $canvas->gridRowCell($this->getHtmlId() . '_' . $day . '_header', $classnames, array($daySpan));
			$date->add(1, BAB_DATETIME_DAY);
		}

		$row = $canvas->gridRow($this->getHtmlId() . '_', array('widget-calendar-header'), $cols);
		return $row; 
	}



	/**
	 * 
	 * @param Widget_Canvas $canvas
	 * @param bab_DateTime $startDate
	 * @return string
	 */
	public function displayCalendarPeriods(Widget_Canvas $canvas, bab_DateTime $startDate)
	{
		$dayStart = bab_DateTime::fromIsoDateTime($startDate->getIsoDate() . ' ' . sprintf('%02d', $this->_displayStartHour) . ':00:00');
		$dayEnd = $dayStart->cloneDate();
		$dayEnd->add($this->_nbDisplayedDays, BAB_DATETIME_DAY);
		
		$dayStartIso = $dayStart->getIsoDate();
		$dayEndIso = $dayEnd->getIsoDate();
		$allPeriods = array();
		foreach ($this->_periods as $period) {
			/* @var $periodStart bab_DateTime */
			$periodStart = clone $period->getStart();
			$periodStartIso = $periodStart->getIsoDate();
			$periodEndIso = $period->getEnd()->getIsoDate();

			$nbChunks = 0;
			while ($periodStartIso <= $periodEndIso) {
				
				if ($periodStartIso >= $dayStartIso && $periodStartIso < $dayEndIso) {

					$nbChunks++;
					$newPeriod = array(
						'period' => $period,
						'iso' => $periodStartIso,
						'startTs' => $periodStart->getTimeStamp(),
	//					'endTs' => $period->getEnd()->getTimeStamp(),
						'column' => 0,
						'isoStart' => $periodStart->getIsoDateTime(),
					);
	
					$periodStart->init($periodStart->getYear(), $periodStart->getMonth(), $periodStart->getDayOfMonth(), 23, 0, 0);
					
	
					if ($periodStartIso === $periodEndIso) {
						$newPeriod['endTs'] = $period->getEnd()->getTimeStamp();
						$newPeriod['splitted'] = ($nbChunks > 1);
	//					$newPeriod['isoEnd'] = $period->getEnd()->getIsoDateTime();
					} else {
						$newPeriod['endTs'] = $periodStart->getTimeStamp();
						$newPeriod['splitted'] = true;
						//					$newPeriod['isoEnd'] = $periodStart->getIsoDateTime();
					}
	//				bab_debug($newPeriod['isoStart'] . ' ' . $newPeriod['isoEnd']);
					
					$allPeriods[] = $newPeriod;
				}
				
				$periodStart->add(1, BAB_DATETIME_DAY);
				$periodStartIso = $periodStart->getIsoDate();
			}
			
			
		}

		$cols = array();
		$cols[] = $canvas->gridRowCell('', array('widget-calendar-periods'));
		for ($day = 0; $day  < $this->_nbDisplayedDays; $day++) {
			$periodDivs = array();

			
			$positionedPeriods = array();
			foreach ($allPeriods as $periodInfo) {
				$period = $periodInfo['period'];
				if ($periodInfo['iso'] === $dayStartIso) {
					$periodStartTs = $periodInfo['startTs'];
					$periodEndTs = $periodInfo['endTs'];
					$freeColumnFound = false;
					for ($col = 0; (!$freeColumnFound) && ($col < count($positionedPeriods)); $col++) {
						$freeColumnFound = true;
						foreach ($positionedPeriods[$col] as $posPeriodInfo) {
							$posPeriod = $posPeriodInfo['period'];
							$posPeriodStartTs = $posPeriod->getStart()->getTimeStamp();
							$posPeriodEndTs = $posPeriod->getEnd()->getTimeStamp();
							if ($periodEndTs >= $posPeriodStartTs && $periodStartTs < $posPeriodEndTs) {
								$freeColumnFound = false;
								break;	
							} 
						}
						if ($freeColumnFound) {
							break;
						}
					}
					$positionedPeriods[$col][] = $periodInfo;
				}
			}

			foreach ($positionedPeriods as $column => $periods) {
				foreach ($periods as $periodInfo) {

					$periodDivClasses = 'widget-calendar-period-content period';
					
					/* @var $period widget_CalendarPeriod */
					$period = $periodInfo['period'];
					$dayStartTs = $dayStart->getTimeStamp();
					$periodStartTs = $period->getStart()->getTimeStamp();
					if ($periodStartTs < $dayStartTs) {
						$periodStartTs = $dayStartTs;
						$periodDivClasses .= ' continue-top';
					}
					$periodEndTs = $period->getEnd()->getTimeStamp();
					if ($periodEndTs > $dayStartTs + 86400) {
						$periodEndTs = $dayStartTs + 86400;
						$periodDivClasses .= ' continue-bottom';
					}
					$periodTop = (($periodStartTs - $dayStartTs) / 3600) * $this->_hourHeight;
					$periodHeight = (($periodEndTs - $periodStartTs) / 3600) * $this->_hourHeight;

					$periodCanvasOptions = Widget_Canvas::Options()->top($periodTop - 2)->height($periodHeight - 2);
					
					if ($period->getBackgroundColor()) {

						$backgroundColor = $period->getBackgroundColor();
						$color = new Widget_Color();
						$color->setHexa($backgroundColor);
						list($r, $g, $b) = $color->getRGB();

						if ($r + $g + $b < 384) {
							$periodDivClasses .= ' dark-background';
						}

						$periodCanvasOptions->backgroundColor($backgroundColor);
					}
					if ($period->isDisplayMode()) {
						$periodDivClasses .= ' display-only';
					}
					if ($periodInfo['splitted']) {
						$periodDivClasses .= ' unmovable';
					}
					
					$periodDiv = $canvas->div('', array($periodDivClasses), array($period->display($canvas)),
													$periodCanvasOptions);
					$w = round(95 / pow($column + 1, 1/2));

					$periodOutDiv = $canvas->div('', array('widget-calendar-period'), array($periodDiv),
													Widget_Canvas::Options()->left(100 - ($w), '%')->width($w, '%')->top($periodTop)->height($periodHeight));
					$periodDivs[] = $periodOutDiv;
				}
			}

			$div = $canvas->div('', array('widget-calendar-periods'), $periodDivs);
			$cols[] = $canvas->gridRowCell($this->getHtmlId() . '_' . $day . '_', array('widget-calendar-periods'), array($div));

			$dayStart->add(1, BAB_DATETIME_DAY);
			$dayStartIso = $dayStart->getIsoDate();
		}

		$row = $canvas->gridRow($this->getHtmlId() . '_periods', array('widget-calendar-periods'), $cols);
		return $row;
	}


	/**
	 * (non-PHPdoc)
	 * @see programs/widgets/Widget_Displayable_Interface#display($canvas)
	 */
	public function display(Widget_Canvas $canvas)
	{
		$startDate = $this->getDate()->cloneDate();

		if ($this->getFirstDayOfWeek() !== null) {
			// We iterate to find the first day of the week before the calendar selected date.
			while ($startDate->getDayOfWeek() != $this->getFirstDayOfWeek()) {
				$startDate->add(-1, BAB_DATETIME_DAY);
			}
		}

		$firstDayOfCalendar = $startDate->getDayOfWeek();

		$sections = array();
		$headerRow = $this->displayCalendarHeader($canvas, $startDate);
		$sections[] = $canvas->gridSection($this->getHtmlId() . '_header_section', array('widget-calendar-section'), array($headerRow));

		$rows = array();

		$rows[] = $this->displayCalendarPeriods($canvas, $startDate);
		
		
		$isoDates = array();
		for ($day = 0; $day  < $this->_nbDisplayedDays; $day++) {
			$isoDates[$day] = $startDate->getIsoDate();
			$startDate->add(1, BAB_DATETIME_DAY);
		}

		for ($hour = $this->_displayStartHour * 2; $hour < ($this->_displayStartHour + $this->_nbDisplayedHours) * 2; $hour++) {
			$cols = array();

			$hourText = ($hour % 2) ? '' : sprintf('%02d',(int)($hour / 2));
			$hourSpan = $canvas->span('', array('widget-calendar-hour'), array($hourText));
			$minutesText = ($hour % 2) ? '' : ':00';
			$minutesSpan = $canvas->span('', array('widget-calendar-minutes'), array($minutesText));

			$hourMinutesSpan = $canvas->span('', array('widget-calendar-hour-minutes'), array($hourSpan, $minutesSpan));
			$div = $canvas->div('', array(), array($hourMinutesSpan));

			$cols['hour'] = $canvas->gridRowCell($this->getHtmlId() . '__' . $hour, array('widget-calendar-header'), array($div));
			$isoTime = sprintf('%02d:%02d',(int)($hour / 2), ($hour % 2) ? 30 : 0);
			for ($day = 0; $day  < $this->_nbDisplayedDays; $day++) {
				if ($day % 7 == (6 - $firstDayOfCalendar) || $day % 7 == (7 - $firstDayOfCalendar)) {
					$cols['day' . $day] = $canvas->gridRowCell($this->getHtmlId() . '_' . $isoDates[$day] . '_' . $isoTime, array('widget-calendar-weekend'));
				} else {
					$cols['day' . $day] = $canvas->gridRowCell($this->getHtmlId() . '_' . $isoDates[$day] . '_' . $isoTime, array());
				}
			}
			$rows[] = $canvas->gridRow($this->getHtmlId() . '_' . $hour, array(($hour % 2) ? 'widget-calendar-half-hour-row' : 'widget-calendar-hour-row'), $cols, Widget_Canvas::Options()->height($this->_hourHeight / 2)); 
		}

		$firstColOptions = Widget_Canvas::Options()->width(5, '%')->minWidth(50, 'px');
		$percents = 95 / $this->getNbDisplayedDays();
		$dayColOptions = Widget_Canvas::Options()->width($percents, '%');

		$colOptions = array($firstColOptions);
		for ($day = 0; $day  < $this->_nbDisplayedDays; $day++) {
			$colOptions[] = $dayColOptions;
		}
		
		$sections[] = $canvas->gridSection($this->getHtmlId() . '_periods_section', array('widget-calendar-periods-section'), $rows);
		$grid = $canvas->grid($this->getHtmlId(), $this->getClasses(), $sections, $colOptions, $this->getCanvasOptions());
		$grid .= $canvas->metadata($this->getId(), $this->getMetadata());

		return $grid;
	}

}





class Widget_CalendarPeriod extends Widget_Frame 
{
	private	$_calendar;
	private $_start;
	private $_end;
	
	private $_backgroundColor;
	
	private $_title;
	private $_location;
	
	private $onDoubleClick;
	
	public function __construct(Widget_Calendar $calendar, bab_DateTime $start, bab_DateTime $end)
	{
		parent::__construct(null, new Widget_VBoxLayout());
		$this->_calendar = $calendar;
		$this->setStart($start);
		$this->setEnd($end);
		$this->setBackgroundColor(null);
	}


	function setEditMode()
	{
		
	}

	/**
	 * Sets the start date of the period.
	 *
	 * @param bab_DateTime $start
	 * @return Widget_CalendarPeriod
	 */
	function setStart(bab_DateTime $start) 
	{
		$this->_start = $start;
		return $this;
	}

	/**
	 * Sets the end date of the period.
	 *
	 * @param bab_DateTime $end
	 * @return Widget_CalendarPeriod
	 */
	function setEnd(bab_DateTime $end) 
	{
		$this->_end = $end;
		return $this;
	}

	/**
	 * Sets the title of the period.
	 *
	 * @param string $title
	 * @return Widget_CalendarPeriod
	 */
	function setTitle($title) 
	{
		$this->_title = $title;
		return $this;
	}

	/**
	 * Sets the location of the period.
	 *
	 * @param string $location
	 * @return Widget_CalendarPeriod
	 */
	function setLocation($location) 
	{
		$this->_location = $location;
		return $this;
	}

	/**
	 * Sets the background color of the period.
	 *
	 * @param string $backgroundColor
	 * @return Widget_CalendarPeriod
	 */
	function setBackgroundColor($backgroundColor) 
	{
		$this->_backgroundColor = $backgroundColor;
		return $this;
	}

	/**
	 * Returns the start date of the period.
	 */
	function getStart()
	{
		return $this->_start;
	}

	/**
	 * Returns the end date of the period.
	 */
	function getEnd()
	{
		return $this->_end;
	}


	/**
	 * Returns the title of the period.
	 */
	function getTitle()
	{
		return $this->_title;
	}


	public function onDoubleClick(widget_Action $action)
	{
		$this->onDoubleClick = $action;
	} 


	/**
	 * Returns the background color of the period.
	 *
	 * @return string
	 */
	function getBackgroundColor() 
	{
		return $this->_backgroundColor;
	}

	
	function getClasses()
	{
		$classes = parent::getClasses();
		$classes[] = 'widget-calendarperiod vevent';
		return $classes;
	}


	function display(Widget_Canvas $canvas)
	{
		if (!isset($this->timeLabel)) {
			if ($this->_start->getIsoDate() === $this->_end->getIsoDate()) {
				$this->timeLabel = new Widget_Label(sprintf('%02d:%02d - %02d:%02d', $this->_start->getHour(), $this->_start->getMinute(), $this->_end->getHour(), $this->_end->getMinute()));
			} else {
				$this->timeLabel = new Widget_Label(sprintf('%02d/%02d %02d:%02d - %02d/%02d %02d:%02d', $this->_start->getDayOfMonth(), $this->_start->getMonth(), $this->_start->getHour(), $this->_start->getMinute(), $this->_end->getDayOfMonth(), $this->_end->getMonth(), $this->_end->getHour(), $this->_end->getMinute()));
			}
			$this->timeLabel->addClass('widget-calendar-period-time'); 
			$this->addItem($this->timeLabel);
		}

		if (!isset($this->timeStartLabel)) {
			$this->timeStartLabel = new Widget_Label(sprintf('%s', $this->_start->getIsoDateTime()));
			$this->timeStartLabel->addClass('widget-invisible dtstart');
			$this->addItem($this->timeStartLabel);
		}
	
		if (!isset($this->timeEndLabel)) {
			$this->timeEndLabel = new Widget_Label(sprintf('%s', $this->_end->getIsoDateTime()));
			$this->timeEndLabel->addClass('widget-invisible dtend'); 
			$this->addItem($this->timeEndLabel);
		}
		
		if (!isset($this->locationLabel)) {
			$this->locationLabel = new Widget_Label($this->_location);
			$this->locationLabel->addClass('widget-calendar-period-location location');
			$this->addItem($this->locationLabel);
		}
		
		if (!isset($this->titleLabel)) {
			$this->titleLabel = new Widget_Label($this->_title);
			$this->titleLabel->addClass('widget-calendar-period-title summary');
			$this->addItem($this->titleLabel);
			if (isset($this->onDoubleClick)) {
				$this->setMetadata('doubleClick', $this->onDoubleClick->url());
			}
		}
		return parent::display($canvas);
	}

}
